<?php
/** OLD NOTE
 *   Router:
      - identify the language of request
      - identify the module (including finding the default module) which will handle request
      - identify operation name (using Nodes or directly from path)
      - initialize controller by module and operation name
*     The extensions may: identify areas, rewrite module names, redirect depending on hostname/GeoIP/etc
*
*   All exceptions thrown by router must lead to a 404 page or when required, redirect to login
 *
 */

class Yz_Router_Default
    extends Yz_Class
{

    const DEFAULT_MODULE = 'Index';
    const ERROR_MODULE = 'Error';

    /** not to repeat in sublcasses: one instance of Router allowed per-application
     *  @var self
     */
    protected static $instance;
    /**
     *  @var Yz_HTTP_Request_Interface
     */
    protected  $request;
    /**
     *  @var Yz_HTTP_Response_Interface
     */
    protected  $response;

    /**
     *  @var string
     */
    protected  $path;

    /** The reference to current module
     *  @var Module_Interface
     */
    protected $module;

    protected $modulePath;

    /********************************
     *      Public methods          *
     *******************************/

    public static function getInstance()
    {
        debug();

        // NOTE using self, to ensure only one type of session per application
        $inst =& self::$instance;
        if ( ! $inst )
            $inst = new static;
        return $inst;
    }

    /**
     *  @param Yz_HTTP_Request_Interface
     *  @param Yz_HTTP_Response_Interface
     *  @return Yz_HTTP_Response_Interface
     */
    public function routeRequest(
        Yz_HTTP_Request_Interface $rq,
        Yz_HTTP_Response_Interface $rs
    )
    {
        debug();

        // {{ prevent subsequent calls
        static $routed = false;
        assert('!$routed');
        $routed = true;
        // }}

        $this->request = $rq;
        $this->modulePath = $rq->getWebPrefix();

        $ctlr = $this->doRoute($rq->getPath());

        return $rs->setView($ctlr->getView());
    }


    /**
     *  @param string $path null means use the path from HTTP Request
     *  @return Yz_Controller_Interface
     */
    public function route( $path, Yz_Array $args=null )
    {
        debug();
        assert('is_string($path)');
        $this->modulePath = "";
        return $this->doRoute($path, $args);
    }


    /***************************
     *     Protected methods   *
     ***************************/

    /** __construct: no direct instantiation
     */
    protected function __construct() { }


    /** do the real routing job
     *  @param string $path
     *  @return Yz_HTTP_Response_Interface
     */
    protected function doRoute( $path, Yz_Array $moreArgs=null )
    {
        debug("Route path: '{$path}'");
        $this->path = $path;
        $args = $this->pathToArray($path);
        if ($moreArgs)
            $args->append($moreArgs);
        try {
            return $this
                ->identifyLanguage($args)
                ->identifyModule($args)
                ->execModule($args)
            ;
        } catch (Error $ex) {
            return $this->_errorHandler($ex);
        }
    }


    /**
     *  working on Arguments array
     *
     */
    protected function identifyLanguage(Yz_Array $args)
    {
        debug();
        if (! $args->isEmpty()) {
            $selector = $args[0];
            debug("Testing for language '$selector'");
            if ( I18N::isInstalledLanguage($selector) ) {
                I18N::setCurrentLanguage($selector);

                $this->modulePath .= $args->shift() . '/';
            }
        }
        return $this;
    }

    /**
     *  working on Arguments array
     *  @return this
     */
    protected function identifyModule(Yz_Array $args)
    {
        debug();

        // thoroughly identify module
        if ($args->isEmpty()) {
            $module = $this->selectDefaultModule();
        } else {
            $module = $this->selectModule($args[0]);
            $this->modulePath .= $args->shift() . '/';
        }
        $module->setWebPath($this->modulePath);
        $this->module = $module;
        return $this;
    }

    protected function execModule(Yz_Array $args)
    {
        debug();
        if ($this->request->isPOST())
            $post = new Yz_Hash($_POST);
        else
            $post = null;
        $r = $this->module->selectRouter()->route($args, $post);

        // if module returned false, re-enter routing
        if (false === $r)
            return $this->doRoute('', $args);
        assert('$r instanceof Yz_Controller_Interface');
        return $r;
    }


    /** split path string into non-empty components (arguments) by '/'
     *  @param string $path
     *  @return Yz_Array
     */
    protected function pathToArray($path)
    {
        debug();

        // ### is filtering faster than preg_split ?
        return new Yz_Array( preg_split("~/+~", $path, null, PREG_SPLIT_NO_EMPTY) );
    }


    /** handle exceptions
     *  @return
     */
    protected function _errorHandler(Error $ex)
    {
        debug($ex);
        return Yz::selectModule(static::ERROR_MODULE)
            ->selectRouter()
            ->routeError($ex)
        ;
    }


    /**
     *  @return Yz_Module_WebModule_Interface
     *  @throw RouteError
     */
    protected function selectModule($moduleName)
    {
        $moduleName = Yz_String::toCamelCase($moduleName);
        $module = false;
        $ex = null;
        if (Yz_String::isValidIdentifier($moduleName)) {
            try {
                $module = Yz::selectModule(ucfirst($moduleName));
            } catch (ClassError $ex) {
                // $module remains false
            }
        }
        // TODO possibly load some module for the case of `bad module names`
        // NOTE: can this be done in error handler ???
        if (! ($module instanceof Yz_Module_WebModule_Interface)) {
            throw RouteError::_new_(array(
                'path' => $this->path,
            ), $ex);
        }
        return $module;
    }

    /**
     *  @return Yz_Module_WebModule_Interface
     */
    protected function selectDefaultModule()
    {
        $module = Yz::selectModule( static::DEFAULT_MODULE );
        assert('$module instanceof Yz_Module_WebModule_Interface');
        return $module;
    }


}
